package model

import (
	"encoding/base64"
	"gin-blog/utils/msg"
	"github.com/jinzhu/gorm"
	"golang.org/x/crypto/bcrypt"
	"golang.org/x/crypto/scrypt"
)

// User 用户
type User struct {
	gorm.Model
	// 绑定数据为json格式，长度设置为20，非空
	// validator数据验证规则 required：设置不能为空，min：最小长度为3，max：最大长度为20
	// label标签，校验不通过的时候会显示对应字段的标签
	Username string `gorm:"type:varchar(20);not null" json:"username" validate:"required,min=3,max=20" label:"用户名"`
	Password string `gorm:"type:varchar(100);not null" json:"password" validate:"required,min=3,max=100" label:"密码"`
	// 1为管理员，2为阅读者，gte=1表示需要大于等于1，防止没有输入权限时，权限默认为0
	Role int `gorm:"type:int;default:2" json:"role" validator:"required,gte=1" label:"角色等级"` // 角色
}

/*
与model保持一致
*/

// CheckUser 查询数据库有没有指定用户名
func CheckUser(user *User) int {
	var u User
	// 从user表中查询第一个用户名为name的记录
	db.Where("username = ?", user.Username).First(&u)
	// 在修改时，防止用户名不变导致出错，防止用户名没查询到时给ID赋0值
	if user.ID != u.ID && u.ID != 0 {
		return msg.ERROR_USERNAME_USED
	}
	return msg.SUCCESS
}

// AddUser 添加用户
func AddUser(user *User) int {
	// scrypt密码加密
	//fpwd, scryptErr := ScryptPw(user.Password)
	//if scryptErr != nil {
	//	return msg.ERROR_USER_PWD_ENCRYPT
	//}
	//user.Password = fpwd

	// bcrypt密码加密
	pwdHash, bcryptErr := HashAndSalt(user.Password)
	if bcryptErr != nil {
		return msg.ERROR_USER_PWD_ENCRYPT
	}
	user.Password = pwdHash

	// 插入数据库
	err := db.Create(&user).Error
	if err != nil {
		return msg.ERROR_USER_ADD
	}
	return msg.SUCCESS
}

// GetUsers 查询用户列表
func GetUsers(pageSize int, pageNum int) (users []User, count int) {
	err := db.Limit(pageSize).Offset((pageNum - 1) * pageSize).Find(&users).Count(&count).Error
	// ErrRecordNotFound：没有记录
	if err != nil && err != gorm.ErrRecordNotFound {
		return nil, 0
	}
	return users, count
}

// BeforeSave 钩子函数，相当于在执行前自动调用
//func (u *User) BeforeSave() {
//	pwdHash, bcryptErr := HashAndSalt(u.Password)
//	if bcryptErr != nil {
//		log.Fatal(msg.GetErrorMsg(msg.ERROR_USER_PWD_ENCRYPT))
//	}
//	u.Password = pwdHash
//}

/*
Scrypt加密比Bcrypt加密好，但效率比Bcrypt慢
*/

// ScryptPw 密码加密
func ScryptPw(password string) (fpwd string, err error) {
	const KeyLen = 10
	// 声明盐
	salt := make([]byte, 8)
	salt = []byte{12, 32, 4, 6, 66, 22, 222, 11}

	HashPw, err := scrypt.Key([]byte(password), salt, 16384, 8, 1, KeyLen)
	// base64标准转换
	fpwd = base64.StdEncoding.EncodeToString(HashPw)
	return fpwd, err
}

/*
Bcrypt是一种加盐的加密方法，使用bcrypt进行加密，同一个密码每次生成的hash值都是不相同的。
每次加密的时候首先会生成一个随机数就是盐，之后将这个随机数与密码进行hash。
*/

// HashAndSalt 加密密码
func HashAndSalt(pwdStr string) (pwdHash string, err error) {
	pwd := []byte(pwdStr)
	hash, err := bcrypt.GenerateFromPassword(pwd, bcrypt.MinCost)
	if err != nil {
		return
	}
	pwdHash = string(hash)
	return
}

// ComparePasswords 验证密码，hashedPwd加密后的密码，plainPwd输入的密码
func ComparePasswords(hashedPwd string, plainPwd string) bool {
	byteHash := []byte(hashedPwd)
	bytePwd := []byte(plainPwd)
	err := bcrypt.CompareHashAndPassword(byteHash, bytePwd)
	if err != nil {
		return false
	}
	return true
}

// DeleteUser 删除用户
func DeleteUser(id int) int {
	err := db.Where("id = ?", id).Delete(&User{}).Error
	if err != nil {
		return msg.ERROR_USER_DELETE
	}
	return msg.SUCCESS
}

// EditUser 修改用户，修改密码忽略，在别的地方实现
func EditUser(user *User) int {
	// 可以直接传user进去，也可以传map进去，都会将有改变的值进行更新
	// 区别是传结构体不会更新零值，map会更新零值
	// 由于role的0表示管理员，所以使用map更新数据
	var maps = make(map[string]interface{})
	maps["username"] = user.Username
	maps["role"] = user.Role
	err := db.Model(&User{}).Where("id = ?", user.ID).Update(maps).Error
	if err != nil {
		return msg.ERROR_USER_EDIT
	}
	return msg.SUCCESS
}

// CheckLogin 登陆验证
func CheckLogin(username string, password string) int {
	var user User
	db.Where("username = ?", username).First(&user)
	// 账号判断
	if user.ID == 0 {
		return msg.ERROR_USER_NOT_EXIST
	}
	// 密码判断
	//fpwd, _ := ScryptPw(password)
	//if fpwd != user.Password {
	//	return msg.ERROR_PASSWORD_WRONG
	//}
	if !ComparePasswords(user.Password, password) {
		return msg.ERROR_PASSWORD_WRONG
	}
	// 非管理员无法登录
	if user.Role != 1 {
		return msg.ERROR_USER_NO_AUTHORITY
	}
	return msg.SUCCESS
}
